﻿//StrategyStatistics.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Reporting
{
    using System;
    using System.Collections.Generic;
    using BusinessEntities;

    /// <summary>
    /// Характеристики кривой эквити (ликвидационной стоимости).
    /// </summary>
    public class StrategyStatistics
    {
        /// <summary>
        /// Профит фактор (отношение суммарного выигрыша к суммарному проигрышу)
        /// </summary>
        public double ProfitFactor { get; set; }
        /// <summary>
        /// Профит фактор для лонга (отношение суммарного выигрыша к суммарному проигрышу)
        /// </summary>
        public double ProfitFactorLong { get; set; }
        /// <summary>
        /// Профит фактор для шорта (отношение суммарного выигрыша к суммарному проигрышу)
        /// </summary>
        public double ProfitFactorShort { get; set; }

        /// <summary>
        /// Коэффициент выплат (средняя прибыль прибыльных сделок / средний убыток по убыточным сделкам)
        /// </summary>
        public double PayoffRatio { get; set; }
        /// <summary>
        /// Коэффициент выплат для лонга (средняя прибыль прибыльных сделок / средний убыток по убыточным сделкам)
        /// </summary>
        public double PayoffRatioLong { get; set; }
        /// <summary>
        /// Коэффициент выплат для шорта (средняя прибыль прибыльных сделок / средний убыток по убыточным сделкам)
        /// </summary>
        public double PayoffRatioShort { get; set; }

        /// <summary>
        /// Коэффициент восстановления (чистая прибыль / максимальная просадка)
        /// </summary>
        public double RecoveryFactor { get; set; }
        /// <summary>
        /// Коэффициент восстановления для лонга (чистая прибыль / максимальная просадка)
        /// </summary>
        public double RecoveryFactorLong { get; set; }
        /// <summary>
        /// Коэффициент восстановления для шорта (чистая прибыль / максимальная просадка)
        /// </summary>
        public double RecoveryFactorShort { get; set; }

        /// <summary>
        /// Количество сделок
        /// </summary>
        public int NumberOfTrades { get; set; }
        /// <summary>
        /// Количество сделок лонг
        /// </summary>
        public int NumberOfLongTrades { get; set; }
        /// <summary>
        /// Количество сделок шорт
        /// </summary>
        public int NumberOfShortTrades { get; set; }

        /// <summary>
        /// Количество прибыльных сделок
        /// </summary>
        public int WinningTrades { get; set; }
        /// <summary>
        /// Количество прибыльных сделок лонг
        /// </summary>
        public int WinningLongTrades { get; set; }
        /// <summary>
        /// Количество прибыльных сделок шорт
        /// </summary>
        public int WinningShortTrades { get; set; }

        /// <summary>
        /// Количество убыточных сделок
        /// </summary>
        public int LosingTrades { get; set; }
        /// <summary>
        /// Количество убыточных сделок лонг
        /// </summary>
        public int LosingLongTrades { get; set; }
        /// <summary>
        /// Количество убыточных сделок шорт
        /// </summary>
        public int LosingShortTrades { get; set; }

        /// <summary>
        /// Общий убыток
        /// </summary>
        public decimal GrossLoss { get; set; }
        /// <summary>
        /// Общий убыток для лонга
        /// </summary>
        public decimal GrossLongLoss { get; set; }
        /// <summary>
        /// Общий убыток для шорта
        /// </summary>
        public decimal GrossShortLoss { get; set; }

        /// <summary>
        /// Общая прибыль
        /// </summary>
        public decimal GrossProfit { get; set; }
        /// <summary>
        /// Общая прибыль для лонга
        /// </summary>
        public decimal GrossLongProfit { get; set; }
        /// <summary>
        /// Общая прибыль для шорта
        /// </summary>
        public decimal GrossShortProfit { get; set; }

        /// <summary>
        /// Чистая прибыль
        /// </summary>
        public decimal NetProfit { get; set; }
        /// <summary>
        /// Чистая прибыль для лонга
        /// </summary>
        public decimal NetLongProfit { get; set; }
        /// <summary>
        /// Чистая прибыль для шорта
        /// </summary>
        public decimal NetShortProfit { get; set; }

        /// <summary>
        /// Средняя прибыль/убыток за сделку
        /// </summary>
        public decimal AverageProfit { get; set; }
        /// <summary>
        /// Средняя прибыль/убыток за сделку для лонга
        /// </summary>
        public decimal AverageLongProfit { get; set; }
        /// <summary>
        /// Средняя прибыль/убыток за сделку для шорта
        /// </summary>
        public decimal AverageShortProfit { get; set; }

        /// <summary>
        /// Средняя прибыль за прибыльную сделку
        /// </summary>
        public decimal AverageWinProfit { get; set; }
        /// <summary>
        /// Средняя прибыль за прибыльную сделку в лонг
        /// </summary>
        public decimal AverageWinLongProfit { get; set; }
        /// <summary>
        /// Средняя прибыль за прибыльную сделку в шорт
        /// </summary>
        public decimal AverageWinShortProfit { get; set; }

        /// <summary>
        /// Средний убыток за убыточную сделку
        /// </summary>
        public decimal AverageLoss { get; set; }
        /// <summary>
        /// Средний убыток за убыточную сделку в лонг
        /// </summary>
        public decimal AverageLongLoss { get; set; }
        /// <summary>
        /// Средний убыток за убыточную сделку в шорт
        /// </summary>
        public decimal AverageShortLoss { get; set; }

        /// <summary>
        /// Средняя прибыль в % за прибыльную сделку
        /// </summary>
        public double AverageWinProfitPrc { get; set; }
        /// <summary>
        /// Средняя прибыль в % за прибыльную сделку в лонг
        /// </summary>
        public double AverageWinLongProfitPrc { get; set; }
        /// <summary>
        /// Средняя прибыль в % за прибыльную сделку в шорт
        /// </summary>
        public double AverageWinShortProfitPrc { get; set; }

        /// <summary>
        /// Средний убыток в % за убыточную сделку
        /// </summary>
        public double AverageLossPrc { get; set; }
        /// <summary>
        /// Средний убыток в % за убыточную сделку в лонг
        /// </summary>
        public double AverageLongLossPrc { get; set; }
        /// <summary>
        /// Средний убыток в % за убыточную сделку в шорт
        /// </summary>
        public double AverageShortLossPrc { get; set; }

        /// <summary>
        /// Средняя прибыль/убыток в % за сделку
        /// </summary>
        public double AverageNetProfitPrc { get; set; }
        /// <summary>
        /// Средняя прибыль/убыток в % за сделку в лонг
        /// </summary>
        public double AverageLongNetProfitPrc { get; set; }
        /// <summary>
        /// Средняя прибыль/убыток в % за сделку в шорт
        /// </summary>
        public double AverageShortNetProfitPrc { get; set; }

        /// <summary>
        /// Максимальная абсолютная просадка для всех сделок
        /// </summary>
        public decimal MaxDrawdown { get; set; }
        /// <summary>
        /// Максимальная абсолютная просадка для сделок лонг
        /// </summary>
        public decimal MaxLongDrawdown { get; set; }
        /// <summary>
        /// Максимальная абсолютная просадка для сделок шорт
        /// </summary>
        public decimal MaxShortDrawdown { get; set; }

        /// <summary>
        /// Процент прибыльных сделок
        /// </summary>
        public double WinRate { get; set; }
        /// <summary>
        /// Процент прибыльных сделок в лонг
        /// </summary>
        public double WinLongRate { get; set; }
        /// <summary>
        /// Процент прибыльных сделок в шорт
        /// </summary>
        public double WinShortRate { get; set; }

        /// <summary>
        /// Процент убыточных сделок
        /// </summary>
        public double LossRate { get; set; }
        /// <summary>
        /// Процент убыточных сделок в лонг
        /// </summary>
        public double LossLongRate { get; set; }
        /// <summary>
        /// Процент убыточных сделок в шорт
        /// </summary>
        public double LossShortRate { get; set; }

        /// <summary>
        /// Эквити по всем сделкам
        /// </summary>
        public decimal[] Equity { get; set; }
        /// <summary>
        /// Эквити по лонг сделкам
        /// </summary>
        public decimal[] LongEquity { get; set; }
        /// <summary>
        /// Эквити по шорт сделкам
        /// </summary>
        public decimal[] ShortEquity { get; set; }

        /// <summary>
        /// Эквити медиана
        /// </summary>
        public double[] EquityMedian { get; set; }

        /// <summary>
        /// Коэффициент стратегии
        /// </summary>
        public double SmoothnessFactor { get; set; }



        ///<summary>
        /// Подсчитать параметры эквити
        ///</summary>
        ///<param name="mytrades">Совершённые сделки</param>
        public void Calculate(IEnumerable<MyTrade> mytrades)
        {
            AddTrades(mytrades);
            Calculate();
        }

        private void AddTrades(IEnumerable<MyTrade> mytrades)
        {
            foreach (var t in mytrades)
            {
                _trades[t.Trade.Id] = t.Trade;
            }
        }

        private void Calculate()
        {
            CalculateTradePairs();
            CalculateStat();
            CalculateMedian();
        }

        private void CalculateTradePairs()
        {
            if (_trades.Count < 2) return;
            if (TradePairs.Count > 0) return;

            Trade newtrade;
            var position = 0;
            var tradeList = new LinkedList<Trade>();

            foreach (var kvp in _trades)
            {
                newtrade = kvp.Value;

                var deltaPosition = newtrade.OrderDirection == OrderDirections.Buy ? newtrade.Volume : -newtrade.Volume;
                var newPosition = position + deltaPosition;

                //позиция увеличивается
                if (((position >= 0) && (newPosition > position)) || ((position <= 0) && (newPosition < position)))
                {
                    tradeList.AddLast(newtrade);
                    position += deltaPosition;
                    continue;
                }

                // позиция закрывается в 0
                if (position == -deltaPosition)
                {
                    foreach (var firsttrade in tradeList)
                    {
                        newtrade.Volume = firsttrade.Volume;
                        AddTradePair(firsttrade, newtrade);
                    }
                    tradeList.Clear();
                    position = 0;
                    continue;
                }

                // позиция переворачивается
                if (((position > 0) && (newPosition < 0)) || ((position < 0) && (newPosition > 0)))
                {
                    foreach (var firsttrade in tradeList)
                    {
                        newtrade.Volume = firsttrade.Volume;
                        AddTradePair(firsttrade, newtrade);
                    }
                    tradeList.Clear();
                    newtrade.Volume = Math.Abs(newPosition);
                    tradeList.AddLast(newtrade);
                    position = newPosition;
                    continue;
                }

                //позиция частично закрывается
                //
                if (((newPosition > 0) && (newPosition < position)) || ((newPosition < 0) && (newPosition > position)))
                {
                    var vol2 = newtrade.Volume;
                    var trigger = true;

                    while (trigger)
                    {
                        var firsttrade = tradeList.First.Value;

                        var vol1 = firsttrade.Volume;
                        if (vol2 < vol1)
                        {
                            newtrade.Volume = vol2;
                            firsttrade.Volume = vol2;
                            AddTradePair(firsttrade, newtrade);
                            firsttrade.Volume = vol1 - vol2;
                            break;
                        }
                        if (vol2 == vol1)
                        {
                            AddTradePair(firsttrade, newtrade);
                            tradeList.Remove(firsttrade);
                            break;
                        }
                        if (vol2 > vol1)
                        {
                            newtrade.Volume = vol1;
                            firsttrade.Volume = vol1;
                            AddTradePair(firsttrade, newtrade);
                            tradeList.Remove(firsttrade);
                            newtrade.Volume = vol2 - vol1;
                            vol2 = vol2 - vol1;
                        }
                        if (tradeList.Count == 0) trigger = false;
                    }
                    position = newPosition;
                }
            }
        }

        private void CalculateStat()
        {
            if (TradePairs.Count < 1) return;

            Trade trade1, trade2;
            var index = 0;

            decimal maxEquity = 0;
            decimal maxLongEquity = 0;
            decimal maxShortEquity = 0;

            Equity = new decimal[TradePairs.Count];
            LongEquity = new decimal[TradePairs.Count];
            ShortEquity = new decimal[TradePairs.Count];

            foreach (var kvp in TradePairs)
            {
                trade1 = kvp.Value.FirstTrade;
                trade2 = kvp.Value.SecondTrade;

                double pLprc;
                decimal pl;
                if (trade1.OrderDirection == OrderDirections.Buy)
                {
                    NumberOfLongTrades++;
                    pl = trade2.Price - trade1.Price;
                    pLprc = (double)(trade2.Price - trade1.Price) / (double)(trade1.Price);
                    if (pl > 0)
                    {
                        WinningLongTrades++;
                        GrossLongProfit += pl;
                        AverageWinLongProfitPrc += pLprc;
                    }
                    else
                    {
                        LosingLongTrades++;
                        GrossLongLoss += -pl;
                        AverageLongLossPrc += -pLprc;
                    }

                }

                if (trade1.OrderDirection == OrderDirections.Sell)
                {
                    NumberOfShortTrades++;
                    pl = trade1.Price - trade2.Price;
                    pLprc = (double)(trade1.Price - trade2.Price) / (double)(trade1.Price);
                    if (pl > 0)
                    {
                        WinningShortTrades++;
                        GrossShortProfit += pl;
                        AverageWinShortProfitPrc += pLprc;
                    }
                    else
                    {
                        LosingShortTrades++;
                        GrossShortLoss += -pl;
                        AverageShortLossPrc += -pLprc;
                    }

                }

                maxEquity = maxEquity < Equity[index] ? Equity[index] : maxEquity;
                maxLongEquity = maxLongEquity < LongEquity[index] ? LongEquity[index] : maxLongEquity;
                maxShortEquity = maxShortEquity < ShortEquity[index] ? ShortEquity[index] : maxShortEquity;

                var drawdown = maxEquity - Equity[index];
                var longDrawdown = maxLongEquity - LongEquity[index];
                var shortDrawdown = maxShortEquity - ShortEquity[index];

                MaxDrawdown = MaxDrawdown < drawdown ? drawdown : MaxDrawdown;
                MaxLongDrawdown = MaxLongDrawdown < longDrawdown ? longDrawdown : MaxLongDrawdown;
                MaxShortDrawdown = MaxShortDrawdown < shortDrawdown ? shortDrawdown : MaxShortDrawdown;

                index++;
            }

            WinningTrades = WinningShortTrades + WinningLongTrades;
            LosingTrades = LosingShortTrades + LosingLongTrades;
            NumberOfTrades = NumberOfShortTrades + NumberOfLongTrades;

            GrossLoss = GrossShortLoss + GrossLongLoss;
            GrossProfit = GrossShortProfit + GrossLongProfit;

            NetLongProfit = GrossLongProfit - GrossLongLoss;
            NetShortProfit = GrossShortProfit - GrossShortLoss;
            NetProfit = NetLongProfit + NetShortProfit;

            AverageProfit = NumberOfTrades > 0 ? Math.Round(NetProfit / NumberOfTrades, 2) : 0;
            AverageLongProfit = NumberOfLongTrades > 0 ? Math.Round(NetLongProfit / NumberOfLongTrades, 2) : 0;
            AverageShortProfit = NumberOfShortTrades > 0 ? Math.Round(NetShortProfit / NumberOfShortTrades, 2) : 0;

            AverageWinProfit = WinningTrades > 0 ? Math.Round(GrossProfit / WinningTrades, 2) : 0;
            AverageWinLongProfit = WinningLongTrades > 0 ? Math.Round(GrossLongProfit / WinningLongTrades, 2) : 0;
            AverageWinShortProfit = WinningShortTrades > 0 ? Math.Round(GrossShortProfit / WinningShortTrades, 2) : 0;

            AverageLoss = LosingTrades > 0 ? Math.Round(GrossLoss / LosingTrades, 2) : 0;
            AverageLongLoss = LosingLongTrades > 0 ? Math.Round(GrossLongLoss / LosingLongTrades, 2) : 0;
            AverageShortLoss = LosingShortTrades > 0 ? Math.Round(GrossShortLoss / LosingShortTrades, 2) : 0;

            WinRate = NumberOfTrades > 0 ? Math.Round((WinningTrades * 1.0) / NumberOfTrades * 100.0, 2) : 0;
            WinLongRate = NumberOfLongTrades > 0 ? Math.Round((WinningLongTrades * 1.0) / NumberOfLongTrades * 100.0, 2) : 0;
            WinShortRate = NumberOfShortTrades > 0 ? Math.Round((WinningShortTrades * 1.0) / NumberOfShortTrades * 100.0, 2) : 0;

            LossRate = NumberOfTrades > 0 ? Math.Round((LosingTrades * 1.0) / NumberOfTrades * 100.0, 2) : 0;
            LossLongRate = NumberOfLongTrades > 0 ? Math.Round((LosingLongTrades * 1.0) / NumberOfLongTrades * 100.0, 2) : 0;
            LossShortRate = NumberOfShortTrades > 0 ? Math.Round((LosingShortTrades * 1.0) / NumberOfShortTrades * 100.0, 2) : 0;

            ProfitFactor = GrossLoss != 0 ? Math.Round((double)GrossProfit / (double)GrossLoss, 2) : 0;
            ProfitFactorLong = GrossLongLoss != 0 ? Math.Round((double)GrossLongProfit / (double)GrossLongLoss, 2) : 0;
            ProfitFactorShort = GrossShortLoss != 0 ? Math.Round((double)GrossShortProfit / (double)GrossShortLoss, 2) : 0;

            AverageWinProfitPrc = AverageWinLongProfitPrc + AverageWinShortProfitPrc;
            AverageLossPrc = AverageLongLossPrc + AverageShortLossPrc;

            AverageNetProfitPrc = NumberOfTrades > 0 ? Math.Round((AverageWinProfitPrc - AverageLossPrc) / NumberOfTrades * 100.0, 2) : 0;
            AverageLongNetProfitPrc = NumberOfLongTrades > 0 ? Math.Round((AverageWinLongProfitPrc - AverageLongLossPrc) / NumberOfLongTrades * 100.0, 2) : 0;
            AverageShortNetProfitPrc = NumberOfShortTrades > 0 ? Math.Round((AverageWinShortProfitPrc - AverageShortLossPrc) / NumberOfShortTrades * 100.0, 2) : 0;

            AverageWinProfitPrc = WinningTrades > 0 ? (AverageWinProfitPrc / WinningTrades * 100.0) : 0;
            AverageWinLongProfitPrc = WinningLongTrades > 0 ? (AverageWinLongProfitPrc / WinningLongTrades * 100.0) : 0;
            AverageWinShortProfitPrc = WinningShortTrades > 0 ? (AverageWinShortProfitPrc / WinningShortTrades * 100.0) : 0;

            AverageLossPrc = LosingTrades > 0 ? (AverageLossPrc / LosingTrades * 100.0) : 0;
            AverageShortLossPrc = LosingShortTrades > 0 ? (AverageShortLossPrc / LosingShortTrades * 100.0) : 0;
            AverageLongLossPrc = LosingLongTrades > 0 ? (AverageLongLossPrc / LosingLongTrades * 100.0) : 0;

            PayoffRatio = AverageLossPrc > 0 ? Math.Round(AverageWinProfitPrc / AverageLossPrc, 2) : 0;
            PayoffRatioLong = AverageLongLossPrc > 0 ? Math.Round(AverageWinLongProfitPrc / AverageLongLossPrc, 2) : 0;
            PayoffRatioShort = AverageShortLossPrc > 0 ? Math.Round(AverageWinShortProfitPrc / AverageShortLossPrc, 2) : 0;

            AverageWinProfitPrc = Math.Round(AverageWinProfitPrc, 2);
            AverageWinLongProfitPrc = Math.Round(AverageWinLongProfitPrc, 2);
            AverageWinShortProfitPrc = Math.Round(AverageWinShortProfitPrc, 2);

            AverageLossPrc = Math.Round(AverageLossPrc, 2);
            AverageShortLossPrc = Math.Round(AverageShortLossPrc, 2);
            AverageLongLossPrc = Math.Round(AverageLongLossPrc, 2);

            RecoveryFactor = MaxDrawdown != 0 ? Math.Round((double)NetProfit / (double)MaxDrawdown, 2) : 0;
            RecoveryFactorLong = MaxLongDrawdown != 0 ? Math.Round((double)NetLongProfit / (double)MaxLongDrawdown, 2) : 0;
            RecoveryFactorShort = MaxShortDrawdown != 0 ? Math.Round((double)NetShortProfit / (double)MaxShortDrawdown, 2) : 0;

        }

        private void CalculateMedian()
        {
            if (Equity == null) return;
            if (Equity.Length < 3) return;

            var values = new double[Equity.Length];

            for (var i = 0; i < Equity.Length; i++)
            {
                values[i] = (double)Equity[i];
            }

            double xAvg = 0;
            double yAvg = 0;

            for (var x = 0; x < values.Length; x++)
            {
                xAvg += x;
                yAvg += values[x];
            }

            xAvg = xAvg / values.Length;
            yAvg = yAvg / values.Length;

            double v1 = 0;
            double v2 = 0;

            for (var x = 0; x < values.Length; x++)
            {
                v1 += (x - xAvg) * (values[x] - yAvg);
                v2 += Math.Pow(x - xAvg, 2);
            }

            var a = v1 / v2;
            var b = yAvg - a * xAvg;

            double deltaSum = 0;
            EquityMedian = new double[Equity.Length];
            //"y = ax + b"
            for (var i = 0; i < Equity.Length; i++)
            {
                EquityMedian[i] = a * i + b;
                deltaSum += (EquityMedian[i] - (double)Equity[i]) * (EquityMedian[i] - (double)Equity[i]);
            }
            deltaSum = Math.Sqrt(deltaSum / Equity.Length);
            SmoothnessFactor = Math.Round((double)NetProfit / deltaSum, 2);
        }

        private void AddTradePair(Trade trade1, Trade trade2)
        {
            var tp = new TradePair
            {
                FirstTrade = trade1,
                SecondTrade = trade2
            };
            TradePairs[trade1.Id] = tp;
        }

        private readonly SortedDictionary<long, Trade> _trades = new SortedDictionary<long, Trade>();

        ///<summary>
        /// Упорядоченный словарь пар закрытых сделок
        ///</summary>
        public readonly SortedDictionary<long, TradePair> TradePairs = new SortedDictionary<long, TradePair>();

        ///<summary>
        /// Пары закрытых сделок
        ///</summary>
        public class TradePair
        {
            ///<summary>
            /// Сделка на открытие позиции
            ///</summary>
            public Trade FirstTrade;
            ///<summary>
            /// Сделка на закрытие позиции
            ///</summary>
            public Trade SecondTrade;
        }
    }
}